/*
 * KeywordMap.java - Fast keyword->id map
 * Copyright (C) 1998, 1999 Slava Pestov
 * Copyright (C) 1999 Mike Dillon
 *
 * You may use and modify this package for any purpose. Redistribution is
 * permitted, in both source and binary form, provided that this notice
 * remains intact in all source distributions of this package.
 */

package org.syntax.jedit;

import javax.swing.text.Segment;

import org.syntax.jedit.tokenmarker.Token;

/**
 * A <code>KeywordMap</code> is similar to a hashtable in that it maps keys to
 * values. However, the `keys' are Swing segments. This allows lookups of text
 * substrings without the overhead of creating a new string object.
 * <p>
 * This class is used by <code>CTokenMarker</code> to map keywords to ids.
 * 
 * @author Slava Pestov, Mike Dillon
 * @version $Id: KeywordMap.java,v 1.16 1999/12/13 03:40:30 sp Exp $
 */
public class KeywordMap
{
    /**
     * Creates a new <code>KeywordMap</code>.
     * 
     * @param ignoreCase
     *            True if keys are case insensitive
     */
    public KeywordMap(boolean ignoreCase)
    {
        this(ignoreCase, 52);
        this.ignoreCase = ignoreCase;
    }

    /**
     * Creates a new <code>KeywordMap</code>.
     * 
     * @param ignoreCase
     *            True if the keys are case insensitive
     * @param mapLength
     *            The number of `buckets' to create. A value of 52 will give
     *            good performance for most maps.
     */
    public KeywordMap(boolean ignoreCase, int mapLength)
    {
        this.mapLength = mapLength;
        this.ignoreCase = ignoreCase;
        map = new Keyword[mapLength];
    }

    /**
     * Looks up a key.
     * 
     * @param text
     *            The text segment
     * @param offset
     *            The offset of the substring within the text segment
     * @param length
     *            The length of the substring
     */
    public byte lookup(Segment text, int offset, int length)
    {
        if (length == 0)
            return Token.NULL;
        Keyword k = map[getSegmentMapKey(text, offset, length)];
        while (k != null)
        {
            if (length != k.keyword.length)
            {
                k = k.next;
                continue;
            }
            if (SyntaxUtilities.regionMatches(ignoreCase, text, offset, k.keyword))
                return k.id;
            k = k.next;
        }
        return Token.NULL;
    }

    /**
     * Adds a key-value mapping.
     * 
     * @param keyword
     *            The key
     * @Param id
     *            The value
     */
    public void add(String keyword, byte id)
    {
        int key = getStringMapKey(keyword);
        map[key] = new Keyword(keyword.toCharArray(), id, map[key]);
    }

    /**
     * Returns true if the keyword map is set to be case insensitive, false
     * otherwise.
     */
    public boolean getIgnoreCase()
    {
        return ignoreCase;
    }

    /**
     * Sets if the keyword map should be case insensitive.
     * 
     * @param ignoreCase
     *            True if the keyword map should be case insensitive, false
     *            otherwise
     */
    public void setIgnoreCase(boolean ignoreCase)
    {
        this.ignoreCase = ignoreCase;
    }

    // protected members
    protected int mapLength;

    protected int getStringMapKey(String s)
    {
        return (Character.toUpperCase(s.charAt(0)) + Character.toUpperCase(s.charAt(s.length() - 1))) % mapLength;
    }

    protected int getSegmentMapKey(Segment s, int off, int len)
    {
        return (Character.toUpperCase(s.array[off]) + Character.toUpperCase(s.array[off + len - 1])) % mapLength;
    }

    // private members
    class Keyword
    {
        public Keyword(char[] keyword, byte id, Keyword next)
        {
            this.keyword = keyword;
            this.id = id;
            this.next = next;
        }

        public char[] keyword;

        public byte id;

        public Keyword next;
    }

    private Keyword[] map;

    private boolean ignoreCase;
}
